﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing.Text;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TDPSet
{
    public class TDPSet
    {

        public const int TdpMax = 30;
        public const int TdpMin = 4;
        private static void InternalSetTDP(int watt)
        {
            if (watt < TdpMin) { watt = TdpMin; }
            if (watt > TdpMax) { watt = TdpMax; }
            int argwatt = watt * 1000;
            string execarg = $"--stapm-limit={argwatt} --fast-limit={argwatt} --slow-limit={argwatt} --apu-slow-limit={argwatt}";
            string result = runprocess(execarg);
            Console.WriteLine(result);
            if (!result.Contains("Sucessfully"))
            {
                throw new Exception(result);
            }
        }

        public const int GpuMax = 2600;
        public const int GpuMin = 200;

        private static readonly int[] gpuFreqs = new int[]{
            200,400,550,600,650,700,750,800,850,900,950,1000,
            1050,1100,1150,1200,1250,1300,1350,1400,1450,1500,1550,
            1600,1650,1700,1750,1800,1850,1900,1950,2000,2050,2100,
            2150,2200,2250,2300,2350,2400,2450,2500,2550,2600
        };


        private static void InternalSetGPUClock(int mhz)
        {
            var nearestMhz = 1800;
            for (int i = 0; i < gpuFreqs.Length; i++)
            {
                if (Math.Abs(gpuFreqs[i] - mhz) <= Math.Abs(mhz - nearestMhz))
                {
                    nearestMhz = gpuFreqs[i];
                }
            }

            string execarg = $" --gfx-clk={nearestMhz}";
            string result = runprocess(execarg);
            Console.WriteLine(result);
            if (!result.Contains("Sucessfully"))
            {
                throw new Exception(result);
            }
        }


        private static string runprocess(string args)
        {
            string programDir = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "tool");
            string programName = "ryzenadj.exe";
            ProcessStartInfo psi = new ProcessStartInfo(Path.Combine(programDir, programName), args);
            psi.UseShellExecute = false;
            psi.RedirectStandardOutput = true;
            psi.RedirectStandardError = true;
            psi.RedirectStandardInput = true;
            psi.CreateNoWindow = true;
            psi.WorkingDirectory = programDir;
            var process = Process.Start(psi);
            return process.StandardOutput.ReadToEnd();
        }



        private static Throttler<int> tdpThrottler;
        private static Throttler<int> gpuThrottler;

        public static void SetTDP(int targetTdp)
        {
            tdpThrottler.set(targetTdp);
        }

        public static void SetGPUClock(int targetGpu)
        {
            gpuThrottler.set(targetGpu);
        }

        private static object syncObj = new object();

        internal static void Init()
        {
            tdpThrottler = new Throttler<int>(-1, (tdp) =>
            {
                if (tdp > 0)
                {
                    try
                    {
                        lock (syncObj)
                        {

                            InternalSetTDP(tdp);
                        }
                    }catch(Exception ex)
                    {
                        Console.WriteLine("设置TDP失败：" + ex);
                    }
                }
            });
            gpuThrottler = new Throttler<int>(-1, (gpu) =>
            {
                if (gpu > 0)
                {
                    try
                    {
                        lock(syncObj)
                        {

                            InternalSetGPUClock(gpu);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("设置GPU失败：" + ex);
                    }
                }
            });
        }



        internal static void Uninit()
        {
            tdpThrottler.Dispose();
            gpuThrottler.Dispose();
        }

        private class Throttler<T> : IDisposable {
        
            private T targetValue;
            private int throttleTime = 2000;
            private CancellationTokenSource cancelToken;
            private EventWaitHandle eventWaitHande;
            private Action<T> action;
            private Thread doThread;
            public Throttler(T initialValue,Action<T> callback) {
                cancelToken = new CancellationTokenSource();
                eventWaitHande = new EventWaitHandle(true,EventResetMode.AutoReset); 
                this.action = callback; this.targetValue = initialValue;
                (doThread = new Thread(thread)).Start();
            }

            private void thread()
            {
                while (true)
                {
                    var e = eventWaitHande.WaitOne();
                    if (cancelToken.IsCancellationRequested)
                    {
                        return;
                    }
                    action(targetValue);
                    Thread.Sleep(throttleTime);
                }
            }

            public void set(T newval)
            {
                if (targetValue.Equals(newval)) {
                    return;
                }
                this.targetValue = newval;
                eventWaitHande?.Set();
            }

            public void Dispose()
            {
                cancelToken.Cancel();
                eventWaitHande.Set();
                doThread.Join();
                cancelToken.Dispose();
                eventWaitHande.Dispose();
            }
        }
    }


}
